package com.shelpe.services.lexicon.service;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

import com.shelpe.services.lexicon.constants.Device;
import com.shelpe.services.lexicon.constants.Origin;
import com.shelpe.services.lexicon.entity.AdgroupRecord;
import com.shelpe.services.lexicon.entity.KeywordRecommend;
import com.shelpe.services.lexicon.entity.KeywordRelation;
import com.shelpe.services.lexicon.entity.KeywordTag;
import com.shelpe.services.lexicon.entity.WordInsightMetric;
import com.shelpe.services.lexicon.entity.metrics.RecommendMetrics;
import com.shelpe.services.lexicon.properties.AutoDriveParams;
import com.shelpe.services.lexicon.repository.mongo.KeywordRecommendRepository;
import com.shelpe.services.lexicon.repository.mongo.KeywordRelationRepository;
import com.shelpe.services.lexicon.service.filter.KeywordRecommendsFilter;
import com.shelpe.services.lexicon.service.tag.RecommendTagRule;
import com.shelpe.services.lexicon.simba.RecommendClient;
import com.shelpe.services.lexicon.simba.RecommendClient.RecommendKeywordResponse;
import com.shelpe.services.lexicon.simba.RecommendClient.RecommendWord;
import com.shelpe.services.lexicon.simba.RecommendClient.RecommendWords;
import com.shelpe.services.lexicon.simba.RecommendClient.RelatedWord;
import com.shelpe.services.lexicon.simba.RecommendClient.RelatedWordResponse;
import com.shelpe.services.lexicon.simba.RecommendClient.WordRelationDataList;
import com.shelpe.services.lexicon.simba.SimbaProxyBaseDto;

import lombok.extern.slf4j.Slf4j;
import rx.Observable;
import rx.functions.Action1;

@Service
@Slf4j
public class KeywordRecommendService {
	private final static String[] TEMPLATE = new String[0];
	@Autowired
	private KeywordRecommendRepository keywordRecommendRepository;
	@Autowired
	private KeywordRelationRepository keywordRelationRepository;
	@Autowired
	private RecommendClient recommendClient;
	@Autowired
	private List<RecommendTagRule> recommendTagRules;
	@Autowired
	private List<KeywordRecommendsFilter> recommendFilter;
	@Autowired
	private InsightMetricService insightMetricService;
	@Autowired
	private AutoDriveParams params;
	@Autowired
	private CounterService counterService;
	
	public Page<KeywordRecommend> findRecommends(PageRequest pageRequest, AdgroupRecord record){
		return this.keywordRecommendRepository.findByAdgroupRecordIDAndOrigin(record.getRecordID(), Origin.taobao, pageRequest);
	}
	
	public void syncRecommends(final AdgroupRecord record, final Callback callback){
		log.debug("fetch recommend word from taobao: {}", record);
		int pageNumber = 1;
		int pageSize = 200;
		counterService.increment("meter.shelpe.lexicon.recommend");
		Observable<RecommendKeywordResponse> observer = this.recommendClient.findRecommendKeyword(record.getNickName(),
				record.getAdgroupID(), pageSize, pageNumber, "search_volume");
		observer.subscribe(new Action1<RecommendKeywordResponse>(){
			@Override
			public void call(RecommendKeywordResponse resp) {
				List<KeywordRecommend> recommends = null;
				if(resp != null && resp.getRes() != null && resp.getRes().getPage() != null && resp.getRes().getPage().getList() != null){
					RecommendWords words = resp.getRes().getPage().getList();
					if(words != null && words.getWords() != null){
						log.debug("fetch recommend word from taobao successfully: {}", record);
						recommends = sync2DB(words.getWords(), record);
						log.debug("store recommend word successfully: {}", record);
					}else{
						log.error("no recommends from taobao even if taobao return something: {}", record);
					}
					counterService.increment("meter.shelpe.lexicon.recommend.success");
				}else{
					log.error("fail to findRecommendKeyword {}: {}", record, resp);
					if(resp != null){
						if(resp.getError() != null && resp.getError().getSubCode() != null){
							counterService.increment("meter.shelpe.lexicon.recommend.failure." + resp.getError().getSubCode());
						}else{
							counterService.increment("meter.shelpe.lexicon.recommend.failure.unkownError");
						}
					}else{
						counterService.increment("meter.shelpe.lexicon.recommend.failure.nullRes");
					}
				}
				callback.call(recommends, resp!=null?resp.getError():null);
			}
		},new Action1<Throwable>(){
			@Override
			public void call(Throwable t) {
				log.error("fail to fetch recommends from taobao: {}, {}", record, t);
				counterService.increment("meter.shelpe.lexicon.recommend.throw");
				callback.call(null, null);
			}
		});
	}
	
	public boolean syncRelateds(List<String> words, AdgroupRecord record){
		boolean status = true;
		
		RelatedWordResponse res = this.recommendClient.findRelatedData(words.toArray(TEMPLATE), 100, record.getTanentID());
		if(res != null && res.getRes() != null && res.getRes().getInsightRes() != null 
				&& res.getRes().getInsightRes().getDataList() != null){
			List<KeywordRelation> relations = new ArrayList<KeywordRelation>();
			
			List<WordRelationDataList> relatedWords = res.getRes().getInsightRes().getDataList();
			for(WordRelationDataList relatedWord: relatedWords){
				if(relatedWord.getRelatedWords() != null){
					for(RelatedWord word: relatedWord.getRelatedWords().getWords()){
						KeywordRelation kr = new KeywordRelation();
						kr.setAdgroupID(record.getAdgroupID());
						kr.setAdgroupRecordID(record.getRecordID());
						kr.setSourceWord(relatedWord.getWord());
						kr.setTargetWord(word.getWord());
						kr.setWeight(NumberUtils.toInt(word.getWeight()) * 100);
						kr.setCreateTime(new Date());
						kr.setLastModifiedTime(new Date());
						relations.add(kr);
					}
				}
			}
			
			this.keywordRelationRepository.save(relations);
		}else{
			log.error("fail to findRelatedData {}: {}", record, res);
			status = false;
		}
		
		return status;
	}
	
	public void syncRecommendTags(List<KeywordRecommend> words, AdgroupRecord record){
		if(words != null){
			RecommendTagRule.TagRuleContext context = new RecommendTagRule.TagRuleContext();
			context.setRecommends(words);
			Date yesterday = DateUtils.addDays(DateUtils.truncate(new Date(), Calendar.DATE), -1);
			List<WordInsightMetric> metrics = insightMetricService.findByAdgroupID(record.getAdgroupID(), yesterday, 
					DateUtils.addDays(yesterday, -params.getStateDays()),
					DateUtils.addDays(yesterday, -params.getStateDays() * 2), DateUtils.addDays(yesterday, -params.getStateDays() *3));
			context.setLast4roundMetrics(metrics);
			
			for(KeywordRecommend word: words){
				word.setTags(null);
				this.runTagRules(word, context);
				word.setTagSyncTime(new Date());
				word.setLastModifiedTime(new Date());
				this.keywordRecommendRepository.save(word);
			}
		}
	}
	
	public List<KeywordRecommend> filterRecommends(List<KeywordRecommend> words, AdgroupRecord record, Device device){
		if(this.recommendFilter != null){
			for(KeywordRecommendsFilter filter:this.recommendFilter){
				words = filter.run(words, record, device);
			}
		}
		return words;
	}
	
	public static interface Callback{
		void call(List<KeywordRecommend> recommends, SimbaProxyBaseDto.Error error);
	}
	
	private void runTagRules(KeywordRecommend word,RecommendTagRule.TagRuleContext context){
		for(RecommendTagRule rule: this.recommendTagRules){
			KeywordTag tag = rule.tag(word, context);
			if(tag != null){
				word.addTag(tag);
			}
		}
	}
	
	private List<KeywordRecommend> sync2DB(List<RecommendWord> words, AdgroupRecord record){
		List<KeywordRecommend> recommends = new ArrayList<KeywordRecommend>();
		for(RecommendWord word: words){
			KeywordRecommend kr = new KeywordRecommend();
			kr.setWord(word.getWord());
			kr.setCreateTime(new Date());
			kr.setLastModifiedTime(new Date());
			kr.setOrigin(Origin.taobao);
			kr.setWeight(51);
			
			if(record != null){
				kr.setAdgroupID(record.getAdgroupID());
				kr.setAdgroupRecordID(record.getRecordID());
			}
			
			RecommendMetrics rm = new RecommendMetrics();
			rm.setPertinence(NumberUtils.toInt(word.getPertinence(), -1));
			rm.setAveragePrice(NumberUtils.createBigDecimal(word.getAveragePrice()));
			rm.setSearchVolume(NumberUtils.toInt(word.getSearchVolume(), 0));
			if(record != null){
				rm.setSource(record.getSource());
			}
			kr.setMetric(rm);
			
			this.keywordRecommendRepository.save(kr);
			recommends.add(kr);
		}
		return recommends;
	}
}
